Skip to content

fix(docitem): render frontMatter.head tags so custom canonicals reach built HTML#879

Merged
nehagup merged 1 commit into
mainfrom
chore/seo-imprvmnt-0
Jun 17, 2026
Merged

fix(docitem): render frontMatter.head tags so custom canonicals reach built HTML#879
nehagup merged 1 commit into
mainfrom
chore/seo-imprvmnt-0

Conversation

@amaan-bhati

@amaan-bhati amaan-bhati commented Jun 16, 2026

Copy link
Copy Markdown
Member

Follow-up to #877, which added head frontmatter to the Regression Testing and Black Box Testing glossary pages to override their canonical tags to point at the corresponding Keploy blog posts instead of the docs pages. After that PR was merged and deployed, both pages still showed the self-referential docs canonical in view-source on production - the blog canonical never appeared.

Issue - reference share prod:

regression testing canonical tag issue
bbt canonical tag issue

Why It Wasn't Working

PR #877 added the correct frontmatter to both markdown files:

head:
  - tag: link
    attrs:
      rel: canonical
      href: https://keploy.io/blog/community/regression-testing-an-introductory-guide

The data was there, parsed correctly, and exported by the MDX compiler - but Docusaurus 3.x never renders frontMatter.head for doc pages. Two things were happening:

  1. DocFrontMatterSchema has no head field. The schema in plugin-content-docs/lib/frontMatter.js uses .unknown(), which silently accepts the key without error but nothing downstream ever reads it. The built-in DocItem/Metadata component only passes title, description, keywords, and image to the head -frontMatter.head is ignored entirely.

  2. SiteMetadata always wins. theme-classic/lib/theme/SiteMetadata/index.js contains a CanonicalUrlHeaders component that unconditionally injects a self-referential <link rel="canonical"> on every page based on the current URL. Since nothing was overriding it, this was the only canonical in the built HTML.


The Fix

Added rendering of frontMatter.head inside the existing <Head> block in src/theme/DocItem/index.js:

{Array.isArray(frontMatter.head) &&
  frontMatter.head.map((headTag, i) => {
    if (!headTag?.tag) return null;
    const {tag: tagName, attrs = {}} = headTag;
    return React.createElement(tagName, {key: i, ...attrs});
  })}

This works because DocItem renders deeper in the React component tree than SiteMetadata. React Helmet - which Docusaurus uses for SSG head management - keeps only the last <link rel="canonical"> it encounters across all <Head> components. Since DocItem renders after SiteMetadata, its canonical replaces the self-referential docs one in the final static HTML.


Testing

Important: use npm run serve (static build), not npm start (dev server). The dev server is client-side only -canonical tags are invisible in view-source until JS hydrates, which is not how Google reads the page. Only the static build reflects what view-source on production sees.

Build output check:

npm run build

grep -o "canonical[^>]*>" build/concepts/reference/glossary/regression-testing/index.html
# → canonical" href="https://keploy.io/blog/community/regression-testing-an-introductory-guide">

grep -o "canonical[^>]*>" build/concepts/reference/glossary/black-box-testing/index.html
# → canonical" href="https://keploy.io/blog/community/black-box-testing-and-white-box-testing-a-complete-guide">

Browser check (npm run serve):

  • view-source:http://localhost:3000/docs/concepts/reference/glossary/regression-testing/ → blog canonical ✅
Screenshot 2026-06-16 at 4 50 16 PM
  • view-source:http://localhost:3000/docs/concepts/reference/glossary/black-box-testing/ → blog canonical ✅
Screenshot 2026-06-16 at 4 51 41 PM

Regression check - pages without head frontmatter are unaffected:

grep -o "canonical[^>]*>" build/concepts/reference/glossary/unit-test-automation/index.html
# → canonical" href="https://keploy.io/docs/concepts/reference/glossary/unit-test-automation/">


Sources & References

Upstream Docusaurus references:

Related:

… built HTML

Signed-off-by: amaan-bhati <amaanbhati49@gmail.com>
@nehagup nehagup merged commit 9fc8f6b into main Jun 17, 2026
6 of 7 checks passed
@nehagup nehagup deleted the chore/seo-imprvmnt-0 branch June 17, 2026 12:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants